Skip to content

feat(build): emit one composite plugin root; targets select host projections; remove the plugin target (#555 W1) - #578

Merged
ScriptedAlchemy merged 28 commits into
mainfrom
feat/555-composite-root-w1
Sep 5, 2026
Merged

feat(build): emit one composite plugin root; targets select host projections; remove the plugin target (#555 W1)#578
ScriptedAlchemy merged 28 commits into
mainfrom
feat/555-composite-root-w1

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Wave 1 (S1 + S2) of the composite-plugin plan for #555 — the DECISION + SPEC and the implementation plan comment. Decisions D1, D5, D7 taken as final. This PR lands for W1 by the owner's decision on #555 (04:52 UTC); #569 stays closed and its consumer/docs/test work is ported here (table below).

What W1 delivers

S1 — remove plugin from the public target model (AB4100)

  • config/normalize.ts no longer synthesizes the plugin lowering target; adapters/registry.ts no longer registers pluginAdapter; adapters/plugin.ts is deleted with everything only it used (the Universal hook-wrapper codec, intersectCapabilityStates / unionCapabilityStates / mergeCapabilityEvidence / capabilityBooleanView).
  • targets: ['plugin'] and --target plugin fail as an unknown target (AB4100); create-agent-bundle --target accepts only claude|codex|cursor|portable; nothing emits AGENTS.md.
  • Removed public API: TargetAdapter.lowersConfigExtensions, TargetRegistry.lowersConfigExtension(), NormalizationTargetRegistry.lowersConfigExtension() (only the composite adapter used them). Removed environment variable: AGENT_BUNDLE_HOOK_HOST (generated hook wrappers bake their host; see below).

S2 — one composite root; targets select host projections (AB4103, AB4105, AB4106)

  • build/build.ts stages one root at the artifact output; only the selected hosts' planners run. New build/compose.ts merges byte-identical entries once, fails same-path/different-bytes with AB4103 (projections compared in host-name order, paths in path order), and refuses a host-scoped command or rule another selected host would discover conventionally with AB4105 (D5). config/validate.ts refuses a selection mixing an advanced-TargetRegistry adapter with any other target with AB4106. adapters/composite-layout.ts owns the composite identity (claude+codex), sorted projections, hook-wrapper naming, and the folder-discovery shadow rule.
  • Codex and Cursor hook/MCP documents move beside their manifests (.codex-plugin/hooks.json, .codex-plugin/mcp.json, .cursor-plugin/hooks.json, .cursor-plugin/mcp.json; AB6027, AB6032, AB7320 name those paths); when another selected host claims their conventional path they emit an empty shield document so folder discovery cannot leak the other host's file. Claude keeps hooks/hooks.json + .mcp.json; portable keeps plugin.json + mcp.json.
  • A hook shared by several selected hosts compiles to hooks/<name>.<host>.mjs per host, each wrapper baking its own host; a single-host hook keeps hooks/<name>.mjs. Native hooks are preserved for every selected host.
  • Compiled MCP entries, scripts, routed CLI bins, and MCP Apps compile once and are attributed to the composite identity in build reports and inspect --bundler (BundlerInspectionEntry.target; distPath.root is <output>). INSTALL.md / install.mjs are composed once over the selection. agent-bundle.manifest.json records the selected projections and provenance; agent-bundle.hooks.json indexes the selected hosts. Reordering targets yields byte-identical output.
  • The generated MCP entry accepts events from the selected hosts its server targets (allowedTargets = server.targets ∩ selected); inspect describes that set. The warm event runtime's endpoint is identified by the artifact alone — epoch and root directory — and the invoking host rides each hook request (see the Meta-framework architecture: one application graph, capability-driven projections, one execution kernel #592 audit).

validate / inspect / packed pool judge the composite root

  • Composition diagnostics (AB4103, AB4105, AB4106) are computed once in prepare and reported identically by validate, inspect, and build (tests/composite-rules.test.ts pins the parity, pooled in the integration list). validate --artifact --host-validation and build --host-validation run each host's validator lane over the root itself.
  • Consumers moved to the root: install/doctor (resolveBundleRoot reads the host manifest directly under --from, AB7001 otherwise — no <from>/<host> lookup), mcp run, serve-app, dev proxy --target, the dev epoch store, artifact/skill/script/hook-playground/MCP-session services, the Workbench artifact inspector (one tree per selected host, root .), eval harnesses (Claude/Codex plugin dir = root), rstest/browser.ts, the agent-bundle/test installed harness, examples, and the tests. The packed pool reads the composite root's bin/ and single mcp/ entry.

Composite root layout (examples/host-test, targets: ['claude','codex','cursor','portable'])

artifact/
├── .agents/plugins/marketplace.json          # Codex marketplace
├── .claude-plugin/{plugin.json,marketplace.json}
├── .codex-plugin/{plugin.json,hooks.json,mcp.json}
├── .cursor-plugin/{plugin.json,marketplace.json,hooks.json,mcp.json}
├── .mcp.json                                 # Claude Code MCP document
├── plugin.json, mcp.json                     # portable (Agent Plugins) documents
├── hooks/hooks.json                          # Claude Code hook document
├── hooks/<hook>.mjs                          # hook selected for exactly one host
├── hooks/<hook>.{claude,codex,cursor}.mjs    # hook shared by several selected hosts
├── hooks/hooks-flight.mjs
├── mcp/mcp-<server>-<hash>.mjs (+ -flight)   # compiled once
├── bin/<plugin>.mjs, bin/<plugin>-flight.mjs # routed CLI
├── skills/, commands/, rules/, scripts/, assets/, mcp-apps/   # shared, emitted once when authored
├── INSTALL.md, install.mjs                   # install surface (install.mjs: cursor|portable)
├── agent-bundle.manifest.json                # selected projections + provenance
└── agent-bundle.hooks.json                   # hook index over the selected hosts

Pinned by tests/build-compose.test.ts (exhaustive sorted top-level listing and of each dotfolder, hooks/, scripts/, skills/, commands/; Codex-only / portable-only / Cursor-only cases; byte-identical roots however the targets are ordered; AB4103, AB4105, AB4106).

Diagnostics

Code Change
AB4100 plugin is an unknown target (prose names the retirement).
AB4103 new, build error — same path, different bytes across selected projections.
AB4105 new, build error — a host-scoped command or rule leaks into a conventionally discovered directory of another selected host. Skills are never host-scoped (normalization gives every skill every selected target; a per-host frontmatter extension that changes bytes is AB4103), so the skill branch that could never fire is gone from compose.ts and the docs say so.
AB4106 new, validate/inspect/build error — an advanced-registry adapter selected beside any other target.

Changeset

One changeset, .changeset/555-composite-plugin-root.md: agent-bundle: minor, create-agent-bundle: minor (breaking pre-1.0); summary names the commands, config keys, exports, env variable, and diagnostic codes above and ends (#578). Private packages (agent-bundle-workbench, examples, website) are not named.

Port from #569 → where it landed in #578

Design differences that drive most "different form" rows: #578 relocates Codex/Cursor documents to static paths (no selection-dependent root contract), emits per-host hooks/<name>.<host>.mjs wrappers instead of one host-detecting wrapper (AGENT_BUNDLE_HOOK_HOST), puts Cursor documents under .cursor-plugin/ rather than hooks/hooks-cursor.json, and projects portable at the root (no portable/ view) with AB4103 arbitrating byte conflicts.

#569 item Status Where in #578 / why not
TargetRegistry.root(targets): ArtifactRootContracts, compositeRoot, compositeHostRoot, compositeMcpRuntime, compositeHookContract different form planComposite / composeProjections (build/compose.ts), projectionIdentity / sortedProjections / hookWrapperPath / folderDiscoveryShadowed (adapters/composite-layout.ts); per-host document paths are the adapters' constants (codexArtifactPaths, cursorArtifactPaths). Not public exports.
build/artifact-root.ts (readArtifactTargets, readArtifactRootContracts, hostRootDirectory, hostMcpRuntime) + its test not adopted Selection is read from parseArtifactManifest(...).targets; host paths are constants; hostRoot is always the root.
Composite TargetAdapter replacing adapters/plugin.ts not adopted adapters/plugin.ts deleted; each built-in planner runs and composeProjections merges.
Host-detecting hook wrapper (AGENT_BUNDLE_HOOK_HOST) not adopted Per-host wrappers + shield documents; the variable is removed from the runtime-environment reference (en/zh).
AGENTS.md emitted at the root not adopted Nothing emits it after S1.
AB4104 (subset-selected shared file; Cursor command beside Claude) different form AB4105 for commands/rules with frontmatter targets; Cursor-beside-Claude commands are refused only when the lowered bytes differ (AB4103).
AB4105 (same path, different bytes) landed, renumbered AB4103.
AB4106 (advanced-registry adapter mixed into a selection) landed config/validate.ts compositeRootTargetDiagnostics, reported by validate/inspect/build.
AB4100 special-cased hint for plugin different form Generic Unknown target message; retirement documented in docs/diagnostics.md.
Codex validator follows manifest hooks/mcpServers pointers different form Pins codexArtifactPaths.* (the build always writes them). Shared-root test ported: codex-plugin-validation.test.ts "judges only the Codex documents under .codex-plugin/ in a root shared with Claude (#555)". Pointer-following asymmetry with the Cursor lane is a follow-up.
Cursor validator reads hooks/hooks-cursor.json not adopted Cursor documents under .cursor-plugin/; the Cursor lane resolves both hooks and mcpServers pointers.
--host-validation over the composite root landed api.ts validate --artifact and build --host-validation pass the root as the plugin directory.
install.ts / doctor.ts resolveBundleRoot without <from>/<host> probing landed Manifest directly under --from, AB7001 otherwise. Negative tests ported: install.test.ts "refuses --from that names a directory above the %s plugin root instead of probing into it (#555)" for all three hosts (no host CLI runs), doctor.test.ts "lists Claude plugins from the plugin root --from names, never from a directory nested under it (#555)". AB7001 rewording stays with S3.
Dev epoch store, artifact/inspection/script-catalog/hook-playground/skill-document services, host-install manager, MCP session/run services landed / different form Root = epoch; projectionDigests (dev/artifacts/projection-digest.ts) one digest per selected host; MCP document paths from the adapters' constants.
Eval harnesses (options.artifact.root), test/installed.ts, Workbench e2e landed Codex/Claude plugin dir = root; installed harness reads the composite root as every host's bundle root; Workbench changes are test-only.
Packed pool, host-install fixture (claude+codex+cursor in one root), examples, examples-contract, create-agent-bundle template README and e2e, mcp-conformance pluginRoot, measure-hook-cold-start.mjs landed Commits 27625cb61, e10164a20, c7612b5e5.
#569 composite-root rule tests landed, different form tests/composite-rules.test.ts + tests/build-compose.test.ts.
Extra Codex proof "installed SKILL.md byte-identical to the built root" not ported (minor) Sidecar/skill presence assertions cover discovery.
Docs (diagnostics section, entry-conventions, framework-mode, READMEs, 19 en/zh website pages) landed Every website file #569 touched is touched here, following #578's layout and codes.
Changeset landed Same file name, (#578).

Residual <output>/<target> consumer audit

rg over packages/**/src, examples/**, scripts/**, docs/**, website/docs/**, README.md, .github/workflows: no join(<root>, target.name), artifact/<host>, artifact/plugin, <root>/<host> probing, or 'plugin' target literal in production code. The remaining 'plugin' literals are host CLI subcommands (claude plugin …, codex plugin …), manifest document names, config.plugin, the install receipt field, and the Claude marketplace key. Two stale doc comments (src/test/packed.ts, src/routes/graph.ts) were corrected in this PR; dated plans/audits under docs/superpowers/** and docs/audits/** keep their historical per-host paths. #588's new prepack test (merged from main) asserted the pre-composite host-packs/cursor/runtime/mcp/server.js; it now asserts host-packs/runtime/mcp/server.js.

#592 boundary audit

Audited against the architecture note: every place a target or projection name reaches runtime or application identity, session/state namespaces, the manifest, or generated code that branches on host name.

Fixed in this PR (cheap: rename, scope, or separate context):

# Site Was Now
F1 mcp-server-runtime.ts (createGeneratedRouteMcpServer) lineageHostFor(options.events.target) read the composite identity as a LineageHost for tool-call lineage when the MCP client did not name itself. The entry bakes hosts — the selected hosts whose MCP documents list that server, i.e. the hosts that can have launched it — beside allowedTargets; the fallback assumes the one host when exactly one can, else undefined. GeneratedEventRuntimeBinding.target removed. Three cases in tests/mcp-server-runtime.test.ts.
F2 build/entry-shell.ts, adapters/hook-contract.ts, src/test/installed.ts Event endpoint id was <epoch>:<selection identity>:<root> in the generated entry (EVENT_TARGET) and every hook wrapper (artifactTarget); the selection had become runtime identity. <epoch>:<root> on both sides; GeneratedRouteMcpEntryOptions.target removed; the invoking host still travels on each request and is checked against allowedTargets.
F3 build/entries.ts (planCompiledMcpEntries, planMcpEntriesSurface) targets ?? [options.target] used the composite identity as the host list when the selection was omitted. targets required.
F6 website/docs/{en,zh}/reference/runtime-environment.mdx, tests/generated-route-server.test.ts AGENT_BUNDLE_HOOK_HOST documented as read by generated wrappers; dead env keys in fixtures. Row and keys removed.
F9 rstest/browser.ts, rstest/browser-setup-module.ts, test/browser-registry.ts The browser-app pool registered each compiled MCP App with the selection identity (claude+codex+portable) as its target, which mountBrowserApp used as the preview profile and the McpAppBinding.target the page sees (CI's examples/mcp-app suite caught it). Apps compile once for the whole selection; the registry's target is the host the app mounts as (the target override, or its first declared target the project selects). tests/test-browser-rstest.test.ts covers a two-host selection.
F10 build/compose.ts, build/validate-artifact.ts, install/surface.ts, config/validate.ts, api.ts, build/pack-inventory.ts, adapters/registry.ts The composite install surface (INSTALL.md, install.mjs) and its AB6023/AB6024 requirement, the AB4106 refusal, and validate/build --host-validation decided "built-in host" by name, so an advanced registry's own adapter named portable shipped instructions it never asked for (Codex review P2), could share a root, and was held to the shipped host's validator; the pack inventory re-derived the surface from manifest names. TargetRegistry.builtInHost()/builtInHosts() (and the optional NormalizationTargetRegistry.builtInHost?) judge the shipped adapters by identity; compose, artifact validation, AB4106, and host validation use them; the pack inventory expects exactly the manifested files (every emitted file is manifested and AB6023/AB6024 already judged the surface). tests/build-compose.test.ts builds a custom portable beside the shipped hosts: no surface, no AB6023/AB6024, AB4106 beside Claude, no host-validation report alone.

Not a leak (checked): application identity in agent-bundle.manifest.json is project.packageName/packageVersion with targets[] per host (no composite string); the composite identity on compiled apps/bins/scripts is report metadata (build --json, inspect --bundler), not module contents or env; AB4102 keys per host for bookkeeping only; hooks/index.json target is the host name; install receipts iterate host names; dev services key targetDigests by host name; runtime/plugin-root.ts and state/* take no target input.

Listed, not changed in W1 (each would widen the PR):

# Site What Why it stays
F4 adapters/claude.ts:3097, codex.ts:1118, cursor.ts:578 (selected = model.targets.map(…)hookWrapperPath, folderDiscoveryShadowed) A host projection's output (wrapper file names, empty shield documents) depends on which other hosts were selected — the Projection IR is a function of the selection. The note allows "host planners composing directly into filesystem entries" as the migration step. Removing it means passing composite context into TargetAdapter.plan explicitly or moving wrapper naming and the shadow rule into assembly — S3/S10.
F5 dev/artifacts/projection-digest.ts:10-19, eval/artifact.ts:45 Per-host digests salt the content hash with the host name, so identical bytes yield N distinct digests. Values are persisted (EpochRecord.targetDigests, eval external-binding cache key, trial gates in eval/harness.ts, claude-harness.ts, codex-harness.ts, playground services); changing the hash changes stored formats — S9/S10.
F7 adapters/hook-contract.ts:758,782; mcp-server-runtime.ts:876,894 Host-name gates (target !== 'claude' && …, lineageHostFor, host === 'cursor' && is_background_agent) where lineage.subagent-events capability rows exist in every capability table. Pre-existing on main, not added here; replacing name checks with capability state needs that state baked into wrapper/binding at build time — #592 (3), S9.
F8a build/compose.ts:208 compositeNoticeDelivery Capability resolution (intersection of host advertisements) performed during assembly; every host's wrappers get the intersection. Forced by the single shared MCP entry and hook worker; belongs between Application IR and projection — S9.
F8b build/compose.ts:255 installSurfaceEntries Distribution metadata composed in assembly. Acceptable per #592 §12; S5.

Follow-ups

  • S3AB7001 rewording per D2; uninstall; dev services' relocation-awareness beyond "root = epoch"; eval Codex home layout; F4 (pass composite context into TargetAdapter.plan or move wrapper naming / the shadow rule into assembly).
  • S4 — npm root = plugin root untouched here; feat(build): hold the package build's dist bundles to AB6005 #588's AB6005 package-build scan is merged in and AB6005: also refuse bare createRequire/require/import.meta.resolve loads in compiled modules #591 extends it. The artifact manifest records target names, not adapter identity; consumers that only have the manifest (install, doctor, the packed harness) still key on names — record the built-in projection identity in the manifest (S6).
  • S5 — per-host source-type rendering and distribution forms; F8b.
  • S9/S10 — F5 (content-only digests), F7 (capability state over host-name gates), F8a (capability resolution before assembly); union-wired runtime with per-host wrapper advertisements; per-host views (AB4104 / acceptance 9).
  • Codex host-validation lane does not follow manifest hooks/mcpServers pointers while the Cursor lane does; fine while the build writes the constants, flag for the wave that opens hand-authored Codex roots.
  • compose.ts sameBytes compares copy entries by source path, not content (conservative AB4103 false positive needs two hosts emitting one path from different sources with identical bytes).
  • Hygiene: one reachesSelection helper for the four "targets reach the selection" predicates (build.ts, entries.ts, mcp-apps.ts, inspect-bundler.ts); selectedTargetNames could reuse sortedProjections; isSkillHostisBuiltInHost.

Gate

On 511464dcb (merged with origin/main cfdaecafd#599's docsite gates included): pnpm install --frozen-lockfile && pnpm build && pnpm typecheck && pnpm lint && pnpm test:unit (3912 passed), pnpm check:release, pnpm docs:site:build (locale drift 0 failures, diagnostics coverage, 0 broken links / 28079 anchors, language parity), plus the integration files the review commits touch (build-compose, composite-rules, generated-route-server, prepack, test-browser-rstest) and examples/mcp-app's test:browser-app suite locally. The full pnpm test:integration:run (1116 passed) ran on df35c1349; CI runs it again on the tip. After gh pr update-branch brought in #603 (323dc9bff): pnpm install --frozen-lockfile && pnpm build && pnpm typecheck && pnpm lint && pnpm test:unit (3945 passed). On the pass-4 fix (064a74c2d, 1b770a4f4 — the first push missed ValidateOptions.root, caught by CI's typecheck): pnpm typecheck && pnpm lint, build-compose.test.ts in the integration pool, and check-diagnostics-coverage. CI's integration-1 shard then caught artifact-validator.test.ts's pre-existing expectation that a custom adapter named portable owes AB6023/AB6024 (the name-keyed rule F10 replaced); ac2e92c1f asserts no diagnostics, and the full pnpm test:integration:run (1122 passed) ran locally on that tip.

Self-review

Reviewer: generalPurpose subagent on GPT-5.6 Sol (the change-risk-reviewer needs the TraceDecay daemon, which is intentionally stopped), asked for concrete merge risks only against the diff vs origin/main. Four passes.

Pass 1 (tip f9f61afd5):

  1. Medium — mcp-server-runtime.ts: the lineage fallback infers the root's cardinality from events.allowedTargets, which is server-scoped, so a Claude-only server in a Claude+Codex root falls back to Claude. Dismissed as correct behaviour, comment and test names made precise (df35c1349). allowedTargets = server.targets ∩ selected is exactly the set of hosts whose MCP documents list that server — each adapter writes only servers whose targets include its host (isSelected(server.targets) in claude.ts, codex.ts, cursor.ts, portable.ts) — so those are the hosts that can have spawned the process; a Claude-only server is launched only by Claude, and assuming Claude when the MCP client does not name itself is right. "Composite root ⇒ no fallback" would drop correct lineage for that case. The reviewer verified the adapter gates and agreed in pass 2.
  2. Low — AB7001 changed its trigger (no <from>/<host> lookup) but has no entry in docs/diagnostics.md. Fixed (df35c1349): the AB700x family row names AB7001 and the composite-root trigger (there is no per-code AB700x table on main either; the family row is the documented form for that range).

Reviewer noted as checked with no issue: endpoint-id derivation parity across the generated entry, hook wrappers, and the installed harness; planCompiledMcpEntries callers; the dead skill branch in AB4105; changeset package/API coverage; en/zh parity; added-module reachability.

Pass 2 (tip df35c1349): no concrete merge risks; lineage disposition confirmed against the adapter code; AB7001 row confirmed.

Codex review bot (two threads on df35c1349, both answered in-thread):

  1. P1 — entries.ts: every event route was attached to the first generated-route server, so a root whose hosts launch different servers (Claude→a, Codex→b) refused Codex's wrappers at a while b hosted nothing. Fixed (2d49a9bef): eventRuntimeHosting hosts the runtime in the first generated-route server each selected host's own MCP document lists — the pre-composite per-host rule — so both a and b host it, each accepting every host that reaches the runtime through a hosting server; the endpoint is the artifact's alone, so whichever hosting process owns the socket answers every host's wrappers. hosts is carried separately for the lineage fallback (F1).
  2. P2 — install/surface.ts: built-in hosts identified by name, not adapter identity. Fixed (2d49a9bef, F10). The pack inventory stays name-based (manifest names only) — listed under S4/S6.

Pass 3 (on 68a154e23, GPT-5.6 Sol, read-only, tests not run): four findings, all fixed in 6558432bd/511464dcb:

  1. Medium — config/validate.ts: AB4106 identified built-ins by name, so custom adapters named portable/cursor bypassed the advanced-registry refusal. Fixed: NormalizationTargetRegistry.builtInHost? (implemented by TargetRegistry by adapter identity) drives the check; registries that cannot tell still judge by name.
  2. Medium — api.ts: validate --host-validation and build --host-validation selected the shipped host validators by target name. Fixed: registry.builtInHosts(...) selects them.
  3. Medium — build/pack-inventory.ts: prepack re-derived the install surface from manifest names, so a custom portable adapter's pack would report AB7010. Fixed: the pack expects exactly manifest.files (every emitted file is manifested; AB6023/AB6024 already judged the surface by identity); the existing AB7010 test still fires from the manifest.
  4. Low — changeset omitted the agentBundleBrowserRstest() target semantic (mount host; compile over the whole selection). Fixed in the changeset summary.

Checked clean by the reviewer: hosting rule matches AB4817's per-host predicate; two hosting entries use the runtime's owner/standby takeover (events/ipc.ts); no remaining eventHostId; every GeneratedEventRuntimeBinding construction carries hosts; browser overrides validated against project and app targets; added modules have production importers; en/zh event-runtime docs match.

Pass 4 (on 511464dcb, GPT-5.6 Sol, read-only, tests not run; verifying the pass-3 fixes): two findings, both fixed in 064a74c2d (+ 1b770a4f4):

  1. Warning — tests/build-compose.test.ts: the identity test called validate() on the project root, which never enters host validation, so the "no report for a custom adapter" assertion was vacuous. Fixed: it validates the built custom root with artifact set (no hostValidation report, no errors), and a custom adapter registered as claude is built with hostValidation: true and a runner spy — no spawn, hostValidation: [].
  2. Low — docs/diagnostics.md AB6024 said any cursor/portable selection requires install.mjs, while the validator identity-filters first. Fixed: the row names the shipped cursor/portable adapters by identity.

Checked clean by the reviewer: TargetRegistry is the only production NormalizationTargetRegistry implementer and its identity map covers the four shipped adapters; prepared.registry is the registry the build used; manifest.files carries the install surface (the AB7010 test still fires from it); installSurfaceRequirements keeps its production importer (validate-artifact.ts); the #599 conflict resolution keeps composite paths and passes locale drift and diagnostics coverage; the changeset matches the code.

Earlier self-review of the S1/S2 commits (pre-#569 port) is preserved in the PR history; its findings (per-server allowedTargets, notice-delivery intersection kept, sameBytes by source path, create-agent-bundle bump minor, shield-document docs) are folded into the sections above and the follow-ups.

…555 S1)

- config/normalize.ts no longer synthesizes a plugin lowering target or a
  shared plugin skill document; loweringHosts is a pure isSkillHost filter.
- adapters/registry.ts registers portable, codex, claude, cursor only;
  adapters/plugin.ts is deleted together with the composite-only capability
  helpers (intersect/union/mergeCapabilityEvidence, capabilityBooleanView).
- targets: ['plugin'] and --target plugin fail with the existing AB4100.
- plugin branches removed from api.ts, pack-inventory, package-build,
  entry-shell, events/projection, lifecycle replay, install/surface,
  install/install, create-agent-bundle options/scaffold, capability JSON,
  and the CLI help text.
- Tests: plugin-bundle.test.ts deleted; plugin rows dropped from the
  adapter, install-surface, normalization and lifecycle suites; acceptance 3
  covered by normalization.test.ts and cli.test.ts.
 S2)

build/build.ts stages every selected host projection into one root at
artifactRoot; build/compose.ts merges byte-identical entries once, fails
same-path collisions with AB4103 in host-name/path order, and refuses
host-scoped components another selected host would discover conventionally
with AB4105. Codex and Cursor hook/MCP documents move beside their manifests;
shared hooks compile to hooks/<name>.<host>.mjs; compiled surfaces are
attributed to the sorted composite identity; the artifact manifest records
only the selected projections. Install, doctor, dev services, eval harnesses,
Workbench, examples, and tests follow the composite root.
@changeset-bot

changeset-bot Bot commented Sep 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ac2e92c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
agent-bundle Minor
create-agent-bundle Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Sep 5, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle@578
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@578
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/rsc-markdown-stream@578
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/@agent-bundle/runtime@578

commit: ac2e92c

@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

Closing as superseded by #569, by the owner's decision (asked directly, 2026-09-05 02:1x UTC): #569 implements the same #555 scope, is CI-green and self-reviewed, and now also carries the plan's "delete rather than shim" directive (no <root>/<host> / <root>/plugin probing). The design points where this PR differs — per-host hooks/<name>.<host>.mjs wrappers plus shield documents instead of one host-detecting Claude+Codex wrapper, Cursor documents under .cursor-plugin/ instead of hooks/hooks-cursor.json, the AB4103 numbering — were not adopted; if any of them should be revisited, open a focused issue against the merged layout.

For the later waves of the plan (npm-source marketplace entries, package-root packaging, local-registry proofs): please rebase them on main once #569 lands. What #569 already provides that those waves can build on: TargetRegistry.root(targets) / ArtifactRootContracts (per-host hookContractFor, mcpRuntimeFor, hostRoot), build/artifact-root.ts (readArtifactTargets, readArtifactRootContracts, hostRootDirectory, hostMcpRuntime), the composition diagnostics AB4104/AB4105/AB4106, the Codex host-validation lane following the manifest's hooks/mcpServers pointers, and the migrated packed/host-install/example test suites.

…inspect judge the composite root; port #569 consumer and docs fixes

- config/validate.ts: AB4106 when an adapter registered on an advanced
  TargetRegistry is selected beside any other target; the built-in host list
  moves to adapters/composite-layout.ts and install/surface.ts imports it.
- build/compose.ts: planComposite returns the plan beside its diagnostics;
  dev/project-service.ts prepare uses it so validate, inspect, dev report
  AB4103/AB4105 exactly where build refuses (caught by the composite-rules
  parity test ported from #569).
- Ported from #569 (superseded): tests/support/mcp-conformance.ts pluginRoot,
  tests/skill-document-service.test.ts decoy, examples/audiobook-curator and
  examples/hooks-and-scripts READMEs, scripts/measure-hook-cold-start.mjs.
- tests/composite-rules.test.ts: layout-independent rules from #569 (four-host
  root, commands/ dialect collision, INSTALL.md sections, validate parity).
- Stale per-host wording: Cursor capability evidence, doctor/types/cursor
  comments, create-agent-bundle README, docs/diagnostics.md, canvas sample.
@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

Reopened 2026-09-05 04:2x UTC: this PR lands for #555 Wave 1 — see the decision comment on #555. The 02:23 "superseded by #569" comment above no longer applies. Before leaving draft it absorbs #569's AB4106 and consumer/doc changes, adds composite-rules.test.ts, and merges current main.

Copy link
Copy Markdown
Owner Author

Architecture note from #592. The composite-root direction is correct, but two implementation choices in this PR should not become long-term architecture:

  • artifactTarget/composite target identity should not become application/runtime identity. targets are host projections; shared compiled routes should retain application identity and carry host/projection context separately.
  • Host planners composing directly into filesystem entries is an acceptable migration step, but the intended end state is Application IR -> per-host Projection IR -> common Artifact IR/assembly. Avoid adding new semantics to compose.ts that belong in application normalization or host projection.

Also preserve the #592 constraint that capability requirements, not host-name branching, should become the primary compatibility contract over time.

This does not block the mechanical composite-root work by itself; it flags the boundary to preserve while #555 lands.

…e selection identity (codex+portable), matching planCompiledMcpApps after the #585 merge
…ot-w1

# Conflicts:
#	rstest.integration-tests.ts
…nested-root and shared-root tests (#592 boundary)

The generated MCP entry and every hook wrapper derived the event runtime's
endpoint id from `<epoch>:<selection identity>:<root>`, so the composite
selection (`claude+codex`) had become runtime identity, and the runtime
took `events.target` as the tool-call lineage host fallback, reading the
selection as a host. Both are projection selection leaking into runtime
identity (#592 §2). The endpoint is now `<epoch>:<root>` on both sides
(entry-shell, hook-contract, `agent-bundle/test` installed harness), the
`target` field leaves `GeneratedRouteMcpEntryOptions` and
`GeneratedEventRuntimeBinding`, and the lineage fallback is the one host a
single-projection root serves, or none for a composite root; `entries.ts`
requires the selection instead of defaulting it from the composite name.

`AB4105` never fired for a skill: normalization gives every skill every
selected target and per-host frontmatter extensions collide as `AB4103`
instead, so the dead skill branch leaves `compose.ts` and the diagnostics,
reference, and framework-mode prose say so. `AGENT_BUNDLE_HOOK_HOST` leaves
the runtime-environment reference (en/zh) and the test env fixtures; `api.ts`
reuses `isBuiltInHost`; `compose.ts` reuses `sortedProjections`; stale
`<target>/…` doc comments in `test/packed.ts` and `routes/graph.ts` name the
root layout.

Ports from #569: install refuses `--from` naming a directory above the plugin
root for all three hosts (`AB7001`, no host CLI runs), doctor lists Claude
plugins from the root `--from` names and never from a nested `claude/`, and
the Codex validator judges only `.codex-plugin/*` in a root shared with
Claude's `.mcp.json` and `hooks/hooks.json`. Lane C's docs parity pass:
tree drawings gain `commands/` and `rules/`, the folder-discovery shield
names all three guarded paths, over-wide code samples re-padded to 90
columns, `AB4808`/`AB4809` prose and the rsc-agent-runtime README describe
one composite root.
@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

Heads-up from a trial merge of this branch with main now that #588 (d30d9acb6) has landed — three things a textually clean merge will not surface until CI:

  1. packages/agent-bundle/tests/prepack.test.ts, test accepts a dependency that only a prebuilt payload module imports: it asserts the packed path host-packs/cursor/runtime/mcp/server.js. Under the composite root that path is host-packs/runtime/mcp/server.js (same change this branch makes for host-packs/INSTALL.md in the shared fixture).
  2. packages/agent-bundle/src/build/build.ts: this branch's import line drops deduplicateDiagnostics, but fix(mcp-apps): React plugin on every view, AB4770–AB4772 compile diagnostics, size report, html defaults, reserved-specifier precedence, readable dev output; rsc-agent-runtime AB8206 detail (#572) #585 (on main) still calls it at diagnostics: deepFreeze(deduplicateDiagnostics(compileDiagnostics)).
  3. packages/agent-bundle/tests/mcp-apps-compile.test.ts (from fix(mcp-apps): React plugin on every view, AB4770–AB4772 compile diagnostics, size report, html defaults, reserved-specifier precedence, readable dev output; rsc-agent-runtime AB8206 detail (#572) #585) calls compileMcpApps(apps, { cwd, meta, outDir, target: 'portable', ...options }); with this branch's McpAppSelection, selected is required (selected: ['portable']).

The real conflicts (build.ts compile loop, mcp-apps.ts selection types, examples/rsc-agent-runtime/tests/host-artifacts.test.ts) are all this branch vs #585; #588's own hunks merge cleanly.

…rst generated server; judge built-in hosts by adapter identity for the install surface (#578 review)

- planMcpEntriesSurface no longer attaches every event route to the first
  generated-route server: eventRuntimeHosting hosts the runtime in the first
  generated server each selected host's MCP document lists (one process when
  they agree, one per host otherwise) and every hosting server accepts the
  same allowed set; a Claude-only server in a Claude+Codex root no longer
  refuses Codex wrappers while Codex's own server hosts nothing.
- The generated entry carries `hosts` (the selected hosts whose documents
  list the server) separately from `allowedTargets`; the lineage fallback
  reads `hosts`, so a Claude-only server hosting a two-host runtime still
  assumes Claude for an anonymous MCP client (#592).
- TargetRegistry.builtInHost()/builtInHosts() judge the four shipped adapters
  by identity; compose and artifact validation use it for INSTALL.md and
  install.mjs, so an advanced registry's adapter named `portable` earns no
  install surface it never asked for. The pack inventory (manifest names
  only) keeps the name-based requirement it had on main.
…ion, never as the selection identity

The browser pool compiles every app once for the project's whole selection
(as the build stages it, #555) but the registry's `target` — the preview
profile and the binding's `target` the page sees — is the host the app
mounts as: the override, or the app's first declared target the project
selects, as before. `claude+codex+portable` had leaked into the binding
(#592) and failed examples/mcp-app's browser-app suite in CI.
# Conflicts:
#	docs/diagnostics.md
#	website/docs/en/guide/distribution/installation.mdx
#	website/docs/en/reference/cli.mdx
#	website/docs/en/reference/configuration.mdx
#	website/docs/zh/guide/distribution/installation.mdx
#	website/docs/zh/reference/cli.mdx
#	website/docs/zh/reference/configuration.mdx
… AB4100 lists the four hosts, AB6023/AB6024 and AB7001 describe the one root
…-validation; the pack inventory expects exactly the manifested files

- NormalizationTargetRegistry.builtInHost? lets config/validate.ts refuse a
  custom adapter registered under a built-in host's name beside other targets
  (AB4106) the way compose and validate-artifact already judge it; registries
  that cannot tell still judge by name.
- validate --host-validation and build --host-validation select the shipped
  validators through registry.builtInHosts(), so a custom `claude` or
  `portable` adapter is held to no shipped host's contract.
- pack-inventory.ts no longer re-derives the install surface from manifest
  target names: every emitted file is manifested and AB6023/AB6024 already
  judged the surface by identity, so the pack expects what the manifest lists.
- Changeset names the browser pool's `target` option semantics.
@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

Addendum to the audit above, on tip 511464dcb (the PR body's "#592 boundary audit" table is current):

Fixed since f9f61afd5:

  • The tool-call lineage fallback now reads a hosts set the entry bakes — the selected hosts whose MCP documents list that server, i.e. the hosts that can have launched it — kept separate from allowedTargets (the hosts whose hook wrappers the runtime it hosts accepts). A Claude-only server hosting a Claude+Codex root's runtime accepts both hosts' wrappers yet assumes Claude for an MCP client that does not name itself; a server two hosts list assumes nothing (mcp-server-runtime.ts, build/entry-shell.ts, build/entries.ts).
  • The shared event runtime is hosted per selected host in the first generated-route server that host's own document lists (the per-host rule from before the roots merged), not in "the first generated server of the root"; every hosting process derives the same artifact-only endpoint id and accepts the same allowed set (build/entries.ts eventRuntimeHosting, Codex review P1).
  • The browser-app Rstest pool had registered each compiled MCP App with the selection identity (claude+codex+portable) as its target, which mountBrowserApp used as the preview profile and the McpAppBinding.target the page sees. Apps compile once for the whole selection; the registry's target is the host the app mounts as (rstest/browser.ts, rstest/browser-setup-module.ts).
  • "Built-in host" is judged by adapter identity, not name, wherever a registry is at hand: the composite install surface and its AB6023/AB6024 requirement, the AB4106 refusal, and validate/build --host-validation (TargetRegistry.builtInHost()/builtInHosts(), optional NormalizationTargetRegistry.builtInHost?). The pack inventory expects exactly the manifested files instead of re-deriving the surface from manifest names.

Listed (unchanged from the table above): the artifact manifest records target names, so consumers that only have the manifest — install, doctor, the packed and installed test harnesses — still key on names; recording the built-in projection identity in the manifest is S6 work.

@ScriptedAlchemy
ScriptedAlchemy enabled auto-merge (squash) September 5, 2026 06:59
…-artifact and build --host-validation

The identity test validated the project root, which never enters host
validation; validate the built custom root as an artifact instead, and
build a custom adapter named claude with a runner spy that must not be
spawned. AB6024's row names the shipped cursor/portable adapters by
identity, as the validator judges.
ScriptedAlchemy added a commit that referenced this pull request Sep 5, 2026
…at/592-authoritative-manifest

Resolve docs/diagnostics.md, api.ts, pack-inventory.ts, route-manifest.ts.
Project routes.contracts[] and route.contract into the artifact manifest via
the shared manifest-routes projection; add them to the shipped JSON Schema
and the en/zh reference page (no longer a reserved key).
@ScriptedAlchemy
ScriptedAlchemy merged commit 62b69c0 into main Sep 5, 2026
16 checks passed
@ScriptedAlchemy
ScriptedAlchemy deleted the feat/555-composite-root-w1 branch September 5, 2026 07:38
ScriptedAlchemy added a commit that referenced this pull request Sep 5, 2026
Correct PreparedProject and inspect paths, record the landed #593 route contract, and align the artifact tree with the #578 layout.
ScriptedAlchemy added a commit that referenced this pull request Sep 5, 2026
…step 2) (#597)

* Document the compiler's Application, Projection, and Artifact layers as they exist today (#592 step 2).

* Fix architecture-page claims the reviewer checked against source.

* Correct the public API path and Observed<T> state wording.

* Re-verify the architecture pages against the merged composite-root tree.

Correct PreparedProject and inspect paths, record the landed #593 route contract, and align the artifact tree with the #578 layout.

* Fix architecture claims the reviewer checked against source.

Skills always ship to every selected host, native hook documents are not wrappers, AB6005 admits only Node built-ins, and doctor/install do not parse the artifact manifest.
ScriptedAlchemy added a commit that referenced this pull request Sep 5, 2026
…anifest

The branch already carried #578's head (1b770a4), whose src tree matches
the squash byte for byte; conflicts resolve to the branch side and main's
only later delta (the custom-portable install-surface expectation) is applied.

# Conflicts:
#	docs/diagnostics.md
#	packages/agent-bundle/README.md
#	packages/agent-bundle/src/adapters/portable.ts
#	packages/agent-bundle/src/api.ts
#	packages/agent-bundle/src/build/build.ts
#	packages/agent-bundle/src/build/pack-inventory.ts
#	packages/agent-bundle/src/build/validate-artifact-hooks.ts
#	packages/agent-bundle/src/build/validate-artifact-skills.ts
#	packages/agent-bundle/src/build/validate-artifact.ts
#	packages/agent-bundle/src/dev/artifacts/artifact-inspection-service.ts
#	packages/agent-bundle/src/dev/artifacts/artifact-script-catalog.ts
#	packages/agent-bundle/src/dev/artifacts/artifact-service.ts
#	packages/agent-bundle/src/dev/mcp-session/mcp-session-service.ts
#	packages/agent-bundle/src/dev/playground/hook-playground-service.ts
#	packages/agent-bundle/src/dev/types.ts
#	packages/agent-bundle/src/eval/artifact.ts
#	packages/agent-bundle/src/install/doctor.ts
#	packages/agent-bundle/src/install/install.ts
#	packages/agent-bundle/src/services/mcp-run.ts
#	packages/agent-bundle/src/test/installed.ts
#	packages/agent-bundle/tests/api.test.ts
#	packages/agent-bundle/tests/artifact-inspection-service.test.ts
#	packages/agent-bundle/tests/artifact-validator.test.ts
#	packages/agent-bundle/tests/build-compose.test.ts
#	packages/agent-bundle/tests/composite-rules.test.ts
#	packages/agent-bundle/tests/hook-playground-service.test.ts
#	packages/agent-bundle/tests/hooks.test.ts
#	packages/agent-bundle/tests/host-adapters.test.ts
#	packages/agent-bundle/tests/install.test.ts
#	packages/agent-bundle/tests/mcp.test.ts
#	packages/agent-bundle/tests/prebuilt-payload.test.ts
#	packages/workbench/tests/artifacts-model.test.ts
#	packages/workbench/tests/artifacts-page.test.ts
#	packages/workbench/tests/hook-client.test.ts
#	packages/workbench/tests/hooks-model.test.ts
#	packages/workbench/tests/hooks-page.test.ts
#	packages/workbench/tests/workbench-capabilities.test.ts
#	website/docs/en/guide/distribution/installation.mdx
#	website/docs/en/guide/start/project-structure.mdx
#	website/docs/en/index.mdx
#	website/docs/en/reference/cli.mdx
#	website/docs/en/reference/targets-artifacts.mdx
#	website/docs/zh/guide/distribution/installation.mdx
#	website/docs/zh/guide/start/project-structure.mdx
#	website/docs/zh/index.mdx
#	website/docs/zh/reference/cli.mdx
#	website/docs/zh/reference/targets-artifacts.mdx
ScriptedAlchemy added a commit that referenced this pull request Sep 5, 2026
…a loader

`const pad = createRequire(u)("left-pad")` bound `pad` as a loader, so a later
`pad(…)` call was reported as a computed load; the template-substitution
scan exposed it (package-build.test.ts). The binding regex now requires
the factory call to end the initializer. Adapt generated-module-loads to
#578's composite root (installSurfaceEntries(model, hosts), planHooks
3-arg, allowedTargets/hosts, hookWrapperPath); resolve docs/diagnostics.md
against #590's contract rows.
ScriptedAlchemy added a commit that referenced this pull request Sep 5, 2026
…rs read the composite root through the manifest (#592 step 3, #555 W2/S3) (#604)

* feat(targets): remove the plugin target from the public target model (#555 S1)

- config/normalize.ts no longer synthesizes a plugin lowering target or a
  shared plugin skill document; loweringHosts is a pure isSkillHost filter.
- adapters/registry.ts registers portable, codex, claude, cursor only;
  adapters/plugin.ts is deleted together with the composite-only capability
  helpers (intersect/union/mergeCapabilityEvidence, capabilityBooleanView).
- targets: ['plugin'] and --target plugin fail with the existing AB4100.
- plugin branches removed from api.ts, pack-inventory, package-build,
  entry-shell, events/projection, lifecycle replay, install/surface,
  install/install, create-agent-bundle options/scaffold, capability JSON,
  and the CLI help text.
- Tests: plugin-bundle.test.ts deleted; plugin rows dropped from the
  adapter, install-surface, normalization and lifecycle suites; acceptance 3
  covered by normalization.test.ts and cli.test.ts.

* feat(build): plan the selected projections into one composite root (#555 S2)

build/build.ts stages every selected host projection into one root at
artifactRoot; build/compose.ts merges byte-identical entries once, fails
same-path collisions with AB4103 in host-name/path order, and refuses
host-scoped components another selected host would discover conventionally
with AB4105. Codex and Cursor hook/MCP documents move beside their manifests;
shared hooks compile to hooks/<name>.<host>.mjs; compiled surfaces are
attributed to the sorted composite identity; the artifact manifest records
only the selected projections. Install, doctor, dev services, eval harnesses,
Workbench, examples, and tests follow the composite root.

* feat(build): AB4106 for mixed advanced-registry selections; validate/inspect judge the composite root; port #569 consumer and docs fixes

- config/validate.ts: AB4106 when an adapter registered on an advanced
  TargetRegistry is selected beside any other target; the built-in host list
  moves to adapters/composite-layout.ts and install/surface.ts imports it.
- build/compose.ts: planComposite returns the plan beside its diagnostics;
  dev/project-service.ts prepare uses it so validate, inspect, dev report
  AB4103/AB4105 exactly where build refuses (caught by the composite-rules
  parity test ported from #569).
- Ported from #569 (superseded): tests/support/mcp-conformance.ts pluginRoot,
  tests/skill-document-service.test.ts decoy, examples/audiobook-curator and
  examples/hooks-and-scripts READMEs, scripts/measure-hook-cold-start.mjs.
- tests/composite-rules.test.ts: layout-independent rules from #569 (four-host
  root, commands/ dialect collision, INSTALL.md sections, validate parity).
- Stale per-host wording: Cursor capability evidence, doctor/types/cursor
  comments, create-agent-bundle README, docs/diagnostics.md, canvas sample.

* test(packed): read the composite root's bin/ and single mcp/ entry in the packed pool

* fix(inspect): describe the same event allowed-targets set the build bakes into a generated-route server; AB4105 trigger names skills

* build: carry #585's MCP App compile diagnostics through the composite root; mcp-apps-compile test selects the portable projection

* test(cli): MCP App compile report attributes the view to the composite selection identity (codex+portable), matching planCompiledMcpApps after the #585 merge

* build: identify the event runtime by the artifact alone; port #569's nested-root and shared-root tests (#592 boundary)

The generated MCP entry and every hook wrapper derived the event runtime's
endpoint id from `<epoch>:<selection identity>:<root>`, so the composite
selection (`claude+codex`) had become runtime identity, and the runtime
took `events.target` as the tool-call lineage host fallback, reading the
selection as a host. Both are projection selection leaking into runtime
identity (#592 §2). The endpoint is now `<epoch>:<root>` on both sides
(entry-shell, hook-contract, `agent-bundle/test` installed harness), the
`target` field leaves `GeneratedRouteMcpEntryOptions` and
`GeneratedEventRuntimeBinding`, and the lineage fallback is the one host a
single-projection root serves, or none for a composite root; `entries.ts`
requires the selection instead of defaulting it from the composite name.

`AB4105` never fired for a skill: normalization gives every skill every
selected target and per-host frontmatter extensions collide as `AB4103`
instead, so the dead skill branch leaves `compose.ts` and the diagnostics,
reference, and framework-mode prose say so. `AGENT_BUNDLE_HOOK_HOST` leaves
the runtime-environment reference (en/zh) and the test env fixtures; `api.ts`
reuses `isBuiltInHost`; `compose.ts` reuses `sortedProjections`; stale
`<target>/…` doc comments in `test/packed.ts` and `routes/graph.ts` name the
root layout.

Ports from #569: install refuses `--from` naming a directory above the plugin
root for all three hosts (`AB7001`, no host CLI runs), doctor lists Claude
plugins from the root `--from` names and never from a nested `claude/`, and
the Codex validator judges only `.codex-plugin/*` in a root shared with
Claude's `.mcp.json` and `hooks/hooks.json`. Lane C's docs parity pass:
tree drawings gain `commands/` and `rules/`, the folder-discovery shield
names all three guarded paths, over-wide code samples re-padded to 90
columns, `AB4808`/`AB4809` prose and the rsc-agent-runtime README describe
one composite root.

* test(prepack): #588's prebuilt payload lands in the composite root (host-packs/runtime/…), not under a target partition

* changeset: the event runtime endpoint is the artifact's alone (#592 boundary)

* review: lineage fallback is the one host whose MCP document lists the server; document AB7001's composite-root trigger

Self-review pass 1 read the fallback as inferring the root's cardinality
from `allowedTargets`. It is `server.targets ∩ selected` — the hosts whose
MCP documents list the server, so the hosts that can have spawned it — and a
Claude-only server in a Claude+Codex root is correctly assumed to be Claude's.
The comment and the two test names now say so. `docs/diagnostics.md` names
`AB7001` in the `AB700x` family row: the host manifest sits directly under
`--from`, never under `<from>/<host>`.

* build: host the composite root's event runtime per selected host's first generated server; judge built-in hosts by adapter identity for the install surface (#578 review)

- planMcpEntriesSurface no longer attaches every event route to the first
  generated-route server: eventRuntimeHosting hosts the runtime in the first
  generated server each selected host's MCP document lists (one process when
  they agree, one per host otherwise) and every hosting server accepts the
  same allowed set; a Claude-only server in a Claude+Codex root no longer
  refuses Codex wrappers while Codex's own server hosts nothing.
- The generated entry carries `hosts` (the selected hosts whose documents
  list the server) separately from `allowedTargets`; the lineage fallback
  reads `hosts`, so a Claude-only server hosting a two-host runtime still
  assumes Claude for an anonymous MCP client (#592).
- TargetRegistry.builtInHost()/builtInHosts() judge the four shipped adapters
  by identity; compose and artifact validation use it for INSTALL.md and
  install.mjs, so an advanced registry's adapter named `portable` earns no
  install surface it never asked for. The pack inventory (manifest names
  only) keeps the name-based requirement it had on main.

* wip(manifest): artifact manifest v2 — projections, routes, executables, distribution; hook index folded into executables.hooks (#592 step 3)

* test(browser): mount each MCP App as one host of the composite selection, never as the selection identity

The browser pool compiles every app once for the project's whole selection
(as the build stages it, #555) but the registry's `target` — the preview
profile and the binding's `target` the page sees — is the host the app
mounts as: the override, or the app's first declared target the project
selects, as before. `claude+codex+portable` had leaked into the binding
(#592) and failed examples/mcp-app's browser-app suite in CI.

* test(manifest): retarget hook and MCP suites at the v2 artifact manifest

The hook-index sidecar is gone; these tests now assert executables.hooks
on agent-bundle.manifest.json and pass the required route graph into build().

* test: migrate manifest coverage to v2

* refactor: read install identity from artifact manifest

* feat: inspect authoritative artifact manifest

* docs: fold #599's per-code diagnostics rows into the composite root — AB4100 lists the four hosts, AB6023/AB6024 and AB7001 describe the one root

* feat(cli): resolve MCP --target from the artifact manifest

serve-app and mcp list|invoke|run default to the only projection that
runs the named server, and inspect reports a built-manifest summary
when one exists at the project's artifact output.

* manifest: routes.cli.routes may hold projected MCP tool routes (routes.mcpCommands)

* test(build-compose): TargetRegistry is constructed, keep the value import

* review: judge built-in hosts by adapter identity in AB4106 and --host-validation; the pack inventory expects exactly the manifested files

- NormalizationTargetRegistry.builtInHost? lets config/validate.ts refuse a
  custom adapter registered under a built-in host's name beside other targets
  (AB4106) the way compose and validate-artifact already judge it; registries
  that cannot tell still judge by name.
- validate --host-validation and build --host-validation select the shipped
  validators through registry.builtInHosts(), so a custom `claude` or
  `portable` adapter is held to no shipped host's contract.
- pack-inventory.ts no longer re-derives the install surface from manifest
  target names: every emitted file is manifested and AB6023/AB6024 already
  judged the surface by identity, so the pack expects what the manifest lists.
- Changeset names the browser pool's `target` option semantics.

* docs: AB4106 judges the shipped adapters by identity

* manifest: hooks[].routeId for event-route wrappers; cross-check CLI command route ids; inspect reads the CLI build root; drop unused imports

* workbench: hook playground rows are manifest hook rows (host, kind, routeId)

* test(workbench): hook client fixture carries manifest hook rows

* docs(manifest): agent-bundle.manifest.json v2 reference; consumers read the composite root through the manifest (#592 step 3, #555 W2/S3)

- New reference page website/docs/{en,zh}/reference/artifact-manifest.mdx
  (+ _meta.json): every section and field of the v2 manifest, who writes it,
  who reads it, the shipped JSON Schema and public reader exports, reserved
  keys not yet emitted, the versioning rule, one abbreviated example.
- install / uninstall / doctor: --from is the composite root; identity and the
  host plugin document come from application + projections[host]; AB7001
  reworded (cli.mdx, installation.mdx, package README).
- serve-app / mcp: --target optional, ambiguity names the choices; inspect
  --json gains output.manifest (cli.mdx, mcp.mdx).
- docs/diagnostics.md: AB7001 and AB60xx family rows; new "Artifact manifest
  coherence (AB6039–AB6040)" section; AB6018 reads executables.hooks[].
- Stale statements: agent-bundle.hooks.json sidecar removed from artifact
  trees and prose (index, project-structure, targets-artifacts, hooks,
  entry-conventions); targets rows -> projection rows.
- Changeset .changeset/592-authoritative-manifest.md (agent-bundle minor).

* feat(validate): AB6039/AB6040 manifest coherence lane over the v2 artifact manifest (#592 step 3)

New `src/build/validate-artifact-manifest.ts`, wired into `validateArtifact`
beside the hook and MCP coherence validators and gated on a parsed manifest
whose file table verified (no AB6004), so neither code fires on top of
AB6000/AB6001/AB6004 noise.

AB6039 (error) — manifest section coherence, what the parser cannot know
without the adapter registry or the tree:
- executables.bins[].path|worker, scripts[].path|worker,
  mcpServers[].entry.path|worker, mcpServers[].apps[].path must be direct
  files of the row's host layout (cliBin, scripts, mcpEntries, mcpApps);
  hooks[] stays with AB6018, which already holds it to hookWrappers.
- a route-generated server (routes.servers[] mode 'generated' with routes)
  whose executables.mcpServers[] row is not 'compiled' or carries another name.
- projections[host].documents.mcp / .hooks must name the document the host's
  runtime / hook contract reads; a row listing a host without an MCP runtime
  or without an MCP document.
- the host MCP document and the rows listing that host declare the same
  server names, each with the transport its row records.

AB6040 (error) — host document disagrees with the manifest identity:
- documents.plugin name/version vs application.name/version.
- documents.marketplace name vs projections[host].marketplace.name; a
  marketplace document the projection does not record (the parser already
  refuses the reverse).
- a host document that is not a strict JSON object cannot be proven.

Both are error severity because a consumer acting on the manifest would
install or launch something the tree does not contain.

Tests: tests/artifact-manifest-coherence.test.ts builds one composite root
(claude, codex, cursor, portable; compiled + command + remote MCP servers;
three marketplaces) and forges one disagreement per case with the manifest
re-serialized and the file table re-hashed, asserting exactly one code.

* fix: resolve MCP documents from artifact manifest

* docs(manifest): hooks[].routeId and projected CLI tool routes (en, zh)

* test: exercise host validation by adapter identity through validate --artifact and build --host-validation

The identity test validated the project root, which never enters host
validation; validate the built custom root as an artifact instead, and
build a custom adapter named claude with a runner spy that must not be
spawned. AB6024's row names the shipped cursor/portable adapters by
identity, as the validator judges.

* Ship a JSON Schema for agent-bundle.manifest.json (#592 step 3)

Add schemas/agent-bundle.manifest.schema.json (draft 2020-12, closed keys
at every level, $defs per manifest shape, "present exactly when" rules as
if/then/else) and publish it through package.json `files` and the
`./schemas/agent-bundle.manifest.schema.json` export.

src/build/manifest-schema.ts imports the JSON, deep-freezes it as
`artifactManifestSchema`, and compiles it once with Ajv 2020 strict mode
(strictRequired relaxed for the conditionals) behind
`validateArtifactManifestSchema(value): readonly string[]`; both are
exported from src/api.ts and src/index.ts. The Rslib bundle inlines the
JSON, so dist needs nothing from the packed schemas directory.

src/schemas/ajv-issues.ts now installs ajv-formats, accepts Ajv options,
and owns the shared `compareSchemaIssues` comparator (lifted from
agent-skills/contract.ts) plus `formatSchemaIssue`.

scripts/dist-freshness.mjs counts the schemas directory as an agent-bundle
build input, since the JSON is compiled into dist.

tests/manifest-schema.test.ts checks a populated and a minimal hand-built
manifest against both validators, sweeps every object for delete /
unknown-key / retype mutations asserting parser-schema agreement with a
two-entry documented allowlist, pins the parser-only rules (sorted arrays,
cross-references, digests, runtime floor, npm name and semver validity)
as accepted-by-schema, pins the schema-encoded rules as rejected by both,
and asserts $id, $schema, manifestVersion const, freezing, and the
package.json wiring.

* test: validate --artifact takes the project root too

* validate: manifest coherence judges MCP rows against the MCP lane's single document read; fixtures follow host layouts

* schema: hooks[].routeId, projected CLI tool routes; writer output validates against the shipped schema; changeset names #604

* doctor: AB7306 recovery names the composite root and manifest projection

* review fixes: reciprocal contract binding, ENOENT-only missing manifest, marketplace pointer existence, projection document pointers judged by the host contract, doctor proof over the built root, stale AB6018/AB7001 prose

* validator: judge document→row MCP coherence by built-in host identity; integration expectations for AB6039/AB6040 and the mcp run refusal

* manifest: projections[].builtInHost records the shipped adapter identity; install/doctor and the installed harness key on it, the validator cross-checks it (AB6039), inspect reports it

* test: mcp-probe fixture records the claude adapter identity

* fixtures record built-in identity; advanced-adapter MCP documents own servers the manifest never rowed; inspection projections in the workbench proof

* validator: the coherence lane reuses the contracts the target-contract lane fetched; no registry re-entry after evidence snapshots

* docs: architecture page describes manifest v2 (#597 follow-up); export ArtifactManifestRouteContract types; cli test imports at top

* feat(workbench): project manifest application explorer

* Prove artifact-manifest paths stay relocatable when the composite root moves.

The writer already emitted root-relative POSIX paths; the parser now also
rejects a Windows drive-letter prefix, and a real build proves the bytes
never encode the machine and every reader still works after rename.

* paths: one relocatable-path rule (core/paths isRelocatablePosixPath) shared by the manifest parser and the writer

* refactor(build): serialize manifest from compiler plans

* docs: describe authoritative manifest generation

* manifest: split operational compiler facts into compiler.recordVersion 1

Keep timings, cache keys, adapter revisions, source-input hashes, and other
run metadata out of the public artifact contract so a compiler refactor never
forces a manifestVersion bump.

* readers: consume manifest.compiler for operational facts

Move validator, pack inventory, eval, inspection, and the installed harness
onto compiler.project / provenance / adapters / agentSkills so consumer
surfaces keep reading only the public contract.

* docs: document the public contract vs compiler record

Restructure the artifact-manifest pages and the targets outline around
manifestVersion and compiler.recordVersion, and mention routes.contracts[]
in the authoritative-manifest changeset.

* test: read provenance and compiler.project after the manifest split

Leftover fixture helpers still typed adapter facts onto projections and
asserted files[].sourceInputs / manifest.project on the public contract.

* docs(architecture): no separate coherence pass; AB6010 carries adapter identity

* test: explorer fixture follows the compiler record split

* rstest: drop the deleted coherence test from the integration list

* docs(architecture): public contract vs compiler record; explorer projection

* Add inspect --artifact so a copied composite root is read through the manifest alone.

* fix: trust manifest inventory for installs

* docs: describe authoritative install inventory

* manifest: MCP/hooks pointers come from the adapter runtime and hook contracts; explorer tolerates route-less servers; tests follow the compiler split

* deslop: reuse errorMessage from core/errors, drop dead projectionFor export, fix orphaned doc comment

- manifest-file.ts: replace private describe helper with the existing
  errorMessage from core/errors.ts (helper-before-writing rule)
- manifest.ts: delete projectionFor, exported with no importer anywhere
  (the coherence test defines its own throwing variant)
- manifest-routes.ts: move the artifactRoutesFor doc comment off
  artifactRouteContractFor, where it was stranded as a second docblock

Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>

* fix: reindex derived artifact variants

* docs: explain variant manifest reindexing

* deslop: drop type-bypassing casts, restating comments, and repetitive changeset prose

* manifest: carry the web section through v2 — schema, referenced-path rule, doctor reads it from the parsed manifest

* tests: preflight artifact graph reads hook rows from the manifest, not the removed sidecar

* tests: compiler-evidence fixture supplies the route graph the manifest writer requires

* review: doctor --from keeps AB7001 from the identity reader; mcp run launches the host document's own line, cross-checked against the manifest entry; relocatable proof covers web.apps[].entry

* tests(packed): provenance lives on compiler.provenance; the packed MCP fixture declares its portable projection identity and server row

* Address second self-review: keep AB7001 in doctor, launch host MCP lines verbatim, reject drive paths in schema, cover web entries in relocation proof

* feat(manifest): serialize definePrebuilt payloads as distribution.payloads[] (#630 absorbed)

* chore: deslop merge delta (gpt-5.6-sol-medium, 1 edit)

* Address merge-delta review: payload host-filtering proof, transport on inspection MCP rows, index-based payload location, diagnostics prose

* web-host: read declared projections from manifest v2 projections[].host (dev /web route 404 after #628 merge)

* feat(manifest): record event route execution

* Pin a strict closed-key inventory for manifest v2 and surface payload runtimeDependencies on inspect.

An old closed reader rejects any unknown key, so adding an optional public field is not compatible — the inventory fixture fails until the version bumps, and artifact-only consumers read packages from distribution.payloads[].runtimeDependencies.

* test: add combined manifest proof

* manifest-key-inventory: merge narrowed row properties over the shared $def; regenerate v2 inventory with routes.events[].execution

* combined proof: assert routes.events[].execution from the built manifest

* docs: readers refuse any other manifestVersion in either direction

* manifest: one launch record for compiled MCP servers

`executables.mcpServers[]` rows of kind `compiled` carry `launch`
({ args, entry, env, worker? }) in place of `entry`; `args[]` records the
author's declaration as `artifact` root-relative paths (plugin-root-anchored)
or `literal` values whose tokens the launcher expands. `web.apps[]` drops its
copied `entry`/`args`/`env` and names the compiled server instead; the parser
cross-checks the reference, the files[] rows, and artifact arguments.

The launch types live in `web-host/manifest.ts` (bundled into every plugin
bin) and `build/manifest.ts` imports them; `readWebManifestDocument` returns
`{ hosts, launches, web? }` so `<plugin> web` resolves the App's launch
through the record. Schema, docs (en+zh), changeset clause, and the packed,
relocatable, and unit proofs follow.

* combined proof: assert executables.mcpServers[].launch; bare plugin-data token

* manifest: prebuilt MCP servers carry the same launch record (kind 'prebuilt'), so web Apps on definePrebuilt servers keep working through one record

* deslop: 5 edits

* Honor manifest launch records in mcp run fallback; web reader requires manifestVersion 2; pin proof wording; prebuilt args/env launch coverage

* Rewire the read-only state-root proof to the manifest launch record

* Remove the prebuilt launch test's temporary home

* Anchor the manifest-only mcp run fallback on the durable plugin root; document the lean web reader and optional web key

* Document the lean web reader as the one non-parser consumer

* Locate the declared state root through the installed manifest's MCP pointer; admit the compile evidence record

* Deslop the state-root rewire; state the inherited state-root fallback

* fix(manifest): lean web reader rejects unsupported versions, duplicate server identities, malformed launch and projection rows; reindex refuses compiled files and compile evidence

* test(workbench): avoid terminal close locator race

* test(manifest): combined proof checks compile evidence survives reindex and install

* chore: deslop pass over the reader/reindex delta

* fix(manifest): launch records name indexed bytes only in both readers; copies re-measure to the verified inventory

* test(manifest): match server-name diagnostics

* fix(manifest): the runtime-owned state root is reserved everywhere — AB4741 for payloads, files[] parser and schema, installers

* fix(manifest): one files[] path rule for both readers — never the manifest, the runtime state root, or the install receipt entry

* chore: deslop the post-review reader/evidence delta

---------

Co-authored-by: Ubuntu <zack@ubuntu-main.local>
Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant